/**
 * This file is a part of qloudgen-sec. 
 * You can redistribute qloudgen-sec and/or modify it under the terms of the Lesser GNU General Public License version 3. 
 * qloudgen-sec is distributed WITHOUT ANY WARRANTY. 
 *
 * See the Lesser GNU General Public License for more details at http://www.gnu.org/licenses/. 
 **/
package org.qloudgen.sec.cas.client.session;

import java.util.Arrays;
import java.util.List;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.jasig.cas.client.util.CommonUtils;
import org.jasig.cas.client.util.XmlUtils;
import org.jasig.cas.client.session.SessionMappingStorage;
import org.jasig.cas.client.session.HashMapBackedSessionMappingStorage;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.qloudgen.sec.util.ExceptionUtil;

/**
 * <p>Title: Cas Client Single Sign Out Handler Implementation</p>
 * <p>Description: Wrappered cas client single sign out handler.</p>
 * <p>Copyright: qloudgen/sec 2014</p>
 * @author <a href="mailto:hyq.dd.sun@gmail.com">glue.3M</a>
 */
public final class CasSingleSignOutHandler {
    /**
	 * Default constructor.
	 */
	public CasSingleSignOutHandler() {
	}
	
    /** Logger instance */
    private final Logger logger = LoggerFactory.getLogger(getClass());

    /** Mapping of token IDs and session IDs to HTTP sessions */
    private SessionMappingStorage sessionMappingStorage = new HashMapBackedSessionMappingStorage();

    /** The name of the artifact parameter.  This is used to capture the session identifier. */
    private String artifactParameterName = "ticket";

    /** Parameter name that stores logout request */
    private String logoutParameterName = "logoutRequest";

    private boolean artifactParameterOverPost = false;

    private boolean eagerlyCreateSessions = true;

    private List<String> safeParameters;

    public void setSessionMappingStorage(final SessionMappingStorage storage) {
        this.sessionMappingStorage = storage;
    }

    public void setArtifactParameterOverPost(final boolean artifactParameterOverPost) {
        this.artifactParameterOverPost = artifactParameterOverPost;
    }

    public SessionMappingStorage getSessionMappingStorage() {
        return this.sessionMappingStorage;
    }

    /**
     * @param name Name of the authentication token parameter.
     */
    public void setArtifactParameterName(final String name) {
        this.artifactParameterName = name;
    }

    /**
     * @param name Name of parameter containing CAS logout request message.
     */
    public void setLogoutParameterName(final String name) {
        this.logoutParameterName = name;
    }

    public void setEagerlyCreateSessions(final boolean eagerlyCreateSessions) {
        this.eagerlyCreateSessions = eagerlyCreateSessions;
    }

    /**
     * Initializes the component for use.
     */
    public void init() {
        CommonUtils.assertNotNull(this.artifactParameterName, "artifactParameterName cannot be null.");
        CommonUtils.assertNotNull(this.logoutParameterName, "logoutParameterName cannot be null.");
        CommonUtils.assertNotNull(this.sessionMappingStorage, "sessionMappingStorage cannot be null.");

        if (this.artifactParameterOverPost) {
            this.safeParameters = Arrays.asList(this.logoutParameterName, this.artifactParameterName);
        } else {
            this.safeParameters = Arrays.asList(this.logoutParameterName);
        }
    }

    /**
     * Determines whether the given request contains an authentication token.
     *
     * @param request HTTP reqest.
     *
     * @return True if request contains authentication token, false otherwise.
     */
    public boolean isTokenRequest(final HttpServletRequest request) {
        return CommonUtils.isNotBlank(CommonUtils.safeGetParameter(request, this.artifactParameterName,
                this.safeParameters));
    }

    /**
     * Determines whether the given request is a CAS logout request.
     *
     * @param request HTTP request.
     *
     * @return True if request is logout request, false otherwise.
     */
    public boolean isLogoutRequest(final HttpServletRequest request) {
        return "POST".equals(request.getMethod())
                && !isMultipartRequest(request)
                && CommonUtils.isNotBlank(CommonUtils.safeGetParameter(request, this.logoutParameterName,
                        this.safeParameters));
    }

    /**
     * Associates a token request with the current HTTP session by recording the mapping
     * in the the configured {@link SessionMappingStorage} container.
     * 
     * @param request HTTP request containing an authentication token.
     */
    public void recordSession(final HttpServletRequest request) {
        final HttpSession session = request.getSession(this.eagerlyCreateSessions);

        if (session == null) {
            logger.debug("No session currently exists (and none created).  Cannot record session information for single sign out.");
            return;
        }

        final String token = CommonUtils.safeGetParameter(request, this.artifactParameterName, this.safeParameters);
        logger.debug("Recording session for token...[{},{}]", token , session);

        try {
            this.sessionMappingStorage.removeBySessionById(session.getId());
        } catch (final Exception e) {
            // ignore if the session is already marked as invalid.  Nothing we can do!
        }
        sessionMappingStorage.addSessionById(token, session);
    }

    /**
     * Destroys the current HTTP session for the given CAS logout request.
     *
     * @param request HTTP request containing a CAS logout message.
     */
    public void destroySession(final HttpServletRequest request , final SessionCallbackHandler callbackHandler) {
        final String logoutMessage = CommonUtils.safeGetParameter(request, this.logoutParameterName,
                this.safeParameters);
        logger.trace("Logout request:\n{}", logoutMessage);

        final String token = XmlUtils.getTextForElement(logoutMessage, "SessionIndex");
        if (CommonUtils.isNotBlank(token)) {
            final HttpSession session = this.sessionMappingStorage.removeSessionByMappingId(token);

            if (session != null) {
                String sessionID = session.getId();

                final HttpSession callbackSession = callbackHandler.getSessionById( sessionID );
                
                logger.debug("Invalidating session [{}] for token [{}]", session , token);
                logger.debug("Invalidating session [{}] for token [{}]", callbackSession , token);
                
                Object ticket = callbackSession.getAttribute( SessionLogoutHandler.DEFAULT_PROXY_NAME );
                
                try {
                    //session.invalidate();
                    callbackSession.invalidate();
                } catch (final IllegalStateException e) {
                    logger.debug("Error invalidating session.", e);
                }
                try {
                    request.logout();
                } catch (final ServletException e) {
                    logger.debug("Error performing request.logout.");
                }
                
                try {
                	sessionLogoutHandler.onInvalidateSession( ( String )ticket );
                } catch (final Throwable t) {
                	logger.error( "Current session logout handler is error...\t{}" , ExceptionUtil.get( t ) );
                }
            }
        }
    }

    private boolean isMultipartRequest(final HttpServletRequest request) {
        return request.getContentType() != null && request.getContentType().toLowerCase().startsWith("multipart");
    }
    
	/**
	 * Returns the String representation.
	 *
	 * @return String
	 */
	@Override
	public String toString() {
		StringBuilder builder = new StringBuilder();
		builder.append( "[" );
		builder.append( super.toString() );
		builder.append( "-->" );
		builder.append( "sessionMappingStorage=" ).append( sessionMappingStorage ).append( "," );
		builder.append( "sessionLogoutHandler=" ).append( sessionLogoutHandler );
		builder.append( "]" );
		//
		return builder.toString();
	}

	/**
	 *
	 */
	private SessionLogoutHandler sessionLogoutHandler = new DefaultSessionLogoutHandler();
	
	/**
	 * Sets current session logout handler.
	 *
	 * @param sessionLogoutHandler
	 */
	public void setSessionLogoutHandler(final SessionLogoutHandler sessionLogoutHandler) {
		this.sessionLogoutHandler = sessionLogoutHandler;
	}
	
	/**
	 * Returns current session logout handler.
	 *
	 * @return SessionLogoutHandler
	 */
	public SessionLogoutHandler getSessionLogoutHandler() {
		return sessionLogoutHandler;
	}
	
	/**
	 *
	 */
	private class DefaultSessionLogoutHandler implements SessionLogoutHandler {
		/**
	 	 * After invalidate session.
		 *
		 * @param ticket
		 */
		public void onInvalidateSession(final String ticket) {
			logger.debug( "Default session logout handler on destroy session do nothing...{}" , ticket );
		}
	}
}